import { ConvexHttpClient } from 'convex/browser';
import { api } from '@/convex/_generated/api';
import type { Doc, Id } from '@/convex/_generated/dataModel';
import type { BuiltChatMessage } from '@/types/chat-message';
import { v4 as uuidv4 } from 'uuid';

// Environment validation
if (
  !process.env.NEXT_PUBLIC_CONVEX_URL ||
  !process.env.CONVEX_SERVICE_ROLE_KEY
) {
  throw new Error(
    'NEXT_PUBLIC_CONVEX_URL or CONVEX_SERVICE_ROLE_KEY environment variable is not defined',
  );
}

const convex = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL);

// Types
interface PentestFile {
  path: string;
  data: Buffer;
}

interface ImageInfo {
  filename: string;
  storageId: string;
}

interface ImageContent {
  type: 'image_url';
  image_url: {
    url: string;
    isPath?: boolean;
  };
}

// Constants
const VALID_IMAGE_EXTENSIONS = [
  'jpg',
  'jpeg',
  'png',
  'gif',
  'webp',
  'svg',
  'bmp',
];
const CONTENT_TYPE_MAP: Record<string, string> = {
  'image/jpeg': 'jpg',
  'image/jpg': 'jpg',
  'image/png': 'png',
  'image/gif': 'gif',
  'image/webp': 'webp',
  'image/svg+xml': 'svg',
  'image/bmp': 'bmp',
};

/**
 * Gets file content from Convex storage
 */
async function getFileContentFromStorage(
  fileId: Id<'files'>,
  userId: string,
): Promise<Buffer | null> {
  try {
    const fileMetadata = await convex.query(api.files.getFile, { fileId });

    // Validate file metadata
    if (
      !fileMetadata ||
      fileMetadata.user_id !== userId ||
      !fileMetadata.file_path ||
      fileMetadata.file_path === '' ||
      fileMetadata.file_path.includes('/')
    ) {
      return null;
    }

    // Get file URL from storage
    const fileUrl = await convex.query(
      api.fileStorage.getFileStorageUrlPublic,
      {
        serviceKey: process.env.CONVEX_SERVICE_ROLE_KEY!,
        storageId: fileMetadata.file_path as Id<'_storage'>,
      },
    );

    if (!fileUrl) return null;

    // Fetch and return file content
    const response = await fetch(fileUrl);
    if (!response.ok) return null;

    const arrayBuffer = await response.arrayBuffer();
    return Buffer.from(arrayBuffer);
  } catch (error) {
    console.error('Error getting file content from storage:', error);
    return null;
  }
}

/**
 * Downloads image from URL and returns as Buffer
 */
async function downloadImageFromUrl(url: string): Promise<Buffer | null> {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      console.error(`Failed to download image: ${response.status}`);
      return null;
    }

    const arrayBuffer = await response.arrayBuffer();
    return Buffer.from(arrayBuffer);
  } catch (error) {
    console.error('Error downloading image:', error);
    return null;
  }
}

/**
 * Determines file extension from URL or content type
 */
function getImageExtension(url: string, contentType?: string): string {
  // Try content type first
  if (contentType) {
    const extension = CONTENT_TYPE_MAP[contentType.toLowerCase()];
    if (extension) return extension;
  }

  // Fallback to URL extension
  const urlExtension = url.split('.').pop()?.toLowerCase();
  if (urlExtension && VALID_IMAGE_EXTENSIONS.includes(urlExtension)) {
    return urlExtension === 'jpeg' ? 'jpg' : urlExtension;
  }

  return 'jpg'; // Default
}

/**
 * Creates pentest files from file items
 */
export async function createPentestFileArray(
  localPath: string,
  fileItems: Doc<'file_items'>[],
  userId: string,
): Promise<PentestFile[]> {
  const pentestFiles: PentestFile[] = [];

  for (const fileItem of fileItems) {
    try {
      const buffer = await getFileContentFromStorage(fileItem.file_id, userId);
      if (buffer) {
        pentestFiles.push({
          path: `${localPath}/${fileItem.name}`,
          data: buffer,
        });
      }
    } catch (error) {
      console.error(
        `Error processing file ${fileItem.name} for pentest:`,
        error,
      );
    }
  }

  return pentestFiles;
}

/**
 * Creates attachment references for terminal/pentest agent
 */
export function createAttachmentReferences(
  items: Array<Doc<'file_items'> | { filename: string }>,
  localPath: string,
): string {
  return items
    .map((item) => {
      const filename =
        'name' in item && item.name
          ? item.name
          : 'filename' in item
            ? item.filename
            : 'unknown';
      return `<attachment filename="${filename}" local_path="${localPath}/${filename}" />`;
    })
    .join('\n');
}

/**
 * Gets the last user message from messages array
 */
function getLastUserMessage(
  messages: BuiltChatMessage[],
): BuiltChatMessage | undefined {
  return messages
    .slice()
    .reverse()
    .find((m) => m.role === 'user');
}

/**
 * Gets all user messages from messages array
 */
function getUserMessages(messages: BuiltChatMessage[]): BuiltChatMessage[] {
  return messages.filter((m) => m.role === 'user');
}

/**
 * Extracts image contents from message content
 */
function getImageContents(content: any[]): ImageContent[] {
  return content.filter(
    (item): item is ImageContent => item.type === 'image_url',
  );
}

/**
 * Processes image URL and creates image info
 */
function processImageUrl(
  imageContent: ImageContent,
  imageUrls: Map<string, string>,
): { url: string; info: ImageInfo } | null {
  let imageUrl = imageContent.image_url.url;
  let storageId = '';

  // Handle storage ID path
  storageId = imageContent.image_url.url;
  const url = imageUrls.get(storageId);
  if (!url) {
    console.error('Failed to get URL for storage ID:', storageId);
    return null;
  }
  imageUrl = url;

  const extension = getImageExtension(imageUrl);
  const filename = storageId
    ? `image-${storageId}.${extension}`
    : `image-${uuidv4()}.${extension}`;

  return {
    url: imageUrl,
    info: { filename, storageId },
  };
}

/**
 * Adds attachment references to user messages
 */
function addAttachmentReferences(
  processedMessages: BuiltChatMessage[],
  imageInfoMap: Map<string, ImageInfo>,
  localPath: string,
): void {
  for (const message of processedMessages) {
    if (message.role !== 'user' || !Array.isArray(message.content)) continue;

    const imageContents = getImageContents(message.content);
    if (imageContents.length === 0) continue;

    const attachmentRefs: string[] = [];

    for (const imageContent of imageContents) {
      const imageInfo = imageInfoMap.get(imageContent.image_url.url);
      if (imageInfo) {
        attachmentRefs.push(
          `<attachment filename="${imageInfo.filename}" local_path="${localPath}/${imageInfo.filename}" />`,
        );
      }
    }

    if (attachmentRefs.length > 0) {
      // Ensure content is array format
      if (typeof message.content === 'string') {
        message.content = [{ type: 'text' as const, text: message.content }];
      }

      // Add attachment references
      attachmentRefs.forEach((ref) => {
        (message.content as any[]).push({ type: 'text' as const, text: ref });
      });
    }
  }
}

/**
 * Generates pentest files from the last user message with attachments
 */
export async function generatePentestFilesFromMessages(
  messages: BuiltChatMessage[],
  userId: string,
  localPath = '/home/user',
): Promise<PentestFile[] | undefined> {
  if (!messages.length) return undefined;

  const lastUserMessage = getLastUserMessage(messages);
  if (!lastUserMessage?.attachments?.length) return undefined;

  try {
    const fileIds = lastUserMessage.attachments
      .map((a) => a.file_id)
      .filter(Boolean);
    if (fileIds.length === 0) return undefined;

    // Get all file items in one batch
    const allFileItems = await convex.query(
      api.file_items.getAllFileItemsByFileIds,
      {
        serviceKey: process.env.CONVEX_SERVICE_ROLE_KEY!,
        fileIds,
      },
    );

    if (!allFileItems?.length) return undefined;

    // Filter for last user message files
    const lastUserFileItems = allFileItems.filter((fi) =>
      lastUserMessage.attachments?.some((a) => a.file_id === fi.file_id),
    );

    if (lastUserFileItems.length === 0) return undefined;

    const pentestFiles = await createPentestFileArray(
      localPath,
      lastUserFileItems,
      userId,
    );
    return pentestFiles.length > 0 ? pentestFiles : undefined;
  } catch (error) {
    console.error('Error generating pentest files from messages:', error);
    return undefined;
  }
}

/**
 * Processes images for pentest files and attachment references
 */
export async function processImagesForPentest(
  messages: BuiltChatMessage[],
  processedMessages: BuiltChatMessage[],
  imageUrls: Map<string, string>,
  localPath: string,
): Promise<{ pentestImageFiles?: PentestFile[] }> {
  const lastUserMessage = getLastUserMessage(messages);
  const userMessages = getUserMessages(messages);

  // Create image info map for all user messages
  const imageInfoMap = new Map<string, ImageInfo>();

  for (const userMessage of userMessages) {
    if (!Array.isArray(userMessage.content)) continue;

    const imageContents = getImageContents(userMessage.content);
    for (const imageContent of imageContents) {
      const result = processImageUrl(imageContent, imageUrls);
      if (result) {
        imageInfoMap.set(result.url, result.info);
      }
    }
  }

  // Create pentest files only for last user message
  let pentestImageFiles: PentestFile[] | undefined;

  if (lastUserMessage && Array.isArray(lastUserMessage.content)) {
    const lastUserImageContents = getImageContents(lastUserMessage.content);

    if (lastUserImageContents.length > 0) {
      pentestImageFiles = [];

      for (const imageContent of lastUserImageContents) {
        try {
          const result = processImageUrl(imageContent, imageUrls);
          if (!result) continue;

          const imageBuffer = await downloadImageFromUrl(result.url);
          if (imageBuffer) {
            pentestImageFiles.push({
              path: `${localPath}/${result.info.filename}`,
              data: imageBuffer,
            });
          }
        } catch (error) {
          console.error('Error processing image for pentest:', error);
        }
      }
    }
  }

  // Add attachment references to all user messages
  addAttachmentReferences(processedMessages, imageInfoMap, localPath);

  return { pentestImageFiles };
}
